// Copyright 2012 Jay Young. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview
 *
 * Provides a class responsible for mapping the documented types to the files
 * in which they are defined (and vice versa).
 */

var fs = require('fs');


/**
 * Maps documented types to the files in which they are defined.  This map is
 * used to generate links between the various doc pages, for example where one
 * type uses another as function arguments.
 *
 * @constructor
 */
var DocMap = exports.DocMap = function() {
  /**
   * A map of type strings to the file names in which they are defined.
   * @type {!Object.<string, DocMap.Entry>}
   */
  this.types = {};

  /**
   * A list of doc JSON files.
   * @type {!Array.<string>}
   */
  this.docFiles = [];
};


/**
 * Static factory method that takes the filename of a serialized DocMap
 * and returns an actual instance.
 *
 * @param {string} docMapFile The file to re-hydrate.
 * @return {DocMap} A re-hydrated DocMap instance.
 */
DocMap.fromFile = function(docMapFile) {
  return JSON.parse(fs.read(docMapFile), function(k, v) {
    if (k === '') {
      var docmap = Object.create(DocMap.prototype);
      for (var i in v) {
        if (Object.prototype.hasOwnProperty.call(v, i)) {
          docmap[i] = v[i];
        }
      }

      return docmap;
    }

    return v;
  });
};


/**
 * Enum of the different kinds of types.
 * @enum {string}
 */
DocMap.Kind = {
  CLASS: 'class',

  INTERFACE: 'interface',

  ENUM: 'enum',

  TYPEDEF: 'typedef'
};


/**
 * Record type containing the file in which a type is defined, the kind of
 * type it is, and its super types ("baseType" and "interfaces" for classes,
 * "extendedInterfaces" for interfaces).
 *
 * @typedef {{filename: string, kind: DocMap.Kind, baseType: string,
 *            implemented: (Array.<string>|undefined),
 *            extendedInterfaces: (Array.<string>|undefined)}}
 */
DocMap.Entry;


/**
 * Take a DocInfo object and document its types.
 *
 * @param {DocInfo} docInfo The DocInfo object to document.
 * @param {string} docFile The file that the doc info will be serialized into.
 */
DocMap.prototype.document = function(docInfo, docFile) {
  var {filename, classes, interfaces, enums, typedefs} = docInfo,
      hasOwnProperty = Object.prototype.hasOwnProperty;

  var docFiles = this.docFiles;
  if (docFiles.indexOf(docFile) === -1) {
    docFiles.push(docFile);
  }

  for (var name in classes) {
    if (hasOwnProperty.call(classes, name)) {
      this.declareType(name, filename, DocMap.Kind.CLASS, classes[name]);
    }
  }
  
  for (var name in interfaces) {
    if (hasOwnProperty.call(interfaces, name)) {
      this.declareType(name, filename, DocMap.Kind.INTERFACE, interfaces[name]);
    }
  }
  
  for (var name in enums) {
    if (hasOwnProperty.call(enums, name)) {
      this.declareType(name, filename, DocMap.Kind.ENUM);
    }
  }
  
  for (var name in typedefs) {
    if (hasOwnProperty.call(typedefs, name)) {
      this.declareType(name, filename, DocMap.Kind.TYPEDEF);
    }
  }
};


/**
 * Register a type and the file in which it is defined.
 *
 * @param {string} type The type name.
 * @param {string} filename The file in which type is declared.
 * @param {Object=} desc The descriptor for the type.
 * @param {DocMap.Kind} kind The kind of type.
 */
DocMap.prototype.declareType = function(type, filename, kind, desc) {
  desc = desc || {};

  var types = this.types;
  if (Object.prototype.hasOwnProperty(types, type)) {
    throw Error('Type ' + type + 'previously declared in file ' + types[type] +
                ' being delcared again in ' + fileNames);
  }

  var typeDecl = types[type] = {
    filename: filename,
    kind: kind
  };

  if (kind === DocMap.Kind.CLASS && desc.baseType) {
    // Copy the array to prevent holding it in memory.
    typeDecl.baseType = desc.baseType;
  }

  if (kind === DocMap.Kind.CLASS &&
      desc.interfaces && desc.interfaces.length > 0) {
    // Copy the array to prevent holding it in memory.
    typeDecl.implemented = desc.interfaces.concat();
  }

  if (kind === DocMap.Kind.INTERFACE &&
      desc.extendedInterfaces && desc.extendedInterfaces.length > 0) {
    typeDecl.extendedInterfaces = desc.extendedInterfaces.concat();
  }
};


/**
 * Get a DocInfo object for the given type.
 *
 * @param {string} type The type to fetch the doc info object for.
 */
DocMap.prototype.getDocInfoForType = function(type) {
  var jsFile = this.types[type].filename,
      docFile = require('rdoc/documentor').Documentor.prototype;
};
